/*
 * IM - Image I/O Library
 * Copyright (c) 1996 CENPES/Petrobras, TeCGraf/PUC-Rio.
 * Antonio E. Scuri - scuri@tecgraf.puc-rio.br
 * 
 * IMBMP.C
 * 
 * Contains the implementation of the BMP file format functions.
 *
 * History:
 *          1996.04.05 - Changed the structure of the module to the new format.
 *          1996.06.14 - Finished inicial version.
 *
 */

#include "im.h"
#include "imio.h"
#include "imbmp.h"
#include "binfile.h"
#include "binutil.h"


#include <stdlib.h>
#include <memory.h>



#define BMP_ID              0x4d42  /* BMP "magic" number           */

#define BMP_COMPRESS_RGB        0L      /* No compression               */
#define BMP_COMPRESS_RLE8       1L      /* 8 bits per pixel compression */
#define BMP_COMPRESS_RLE4       2L      /* 4 bits per pixel compression */
#define BMP_BITFIELDS           3L      /* no compression, palette is mask for 16 and 32 bits images */

/* State-machine definitions */
#define BMP_READING 0         /* General READING mode */
#define BMP_ENCODING 1        /* Encoding same-color pixel runs */
#define BMP_ABSMODE 2         /* Absolute-mode encoding */
#define BMP_SINGLE 3          /* Encoding short absolute-mode runs */
#define BMP_ENDOFLINE 4       /* End of scan line detected */

#define BMP_LSN(value)  (unsigned char)((value) & 0x0f)      /* Least-significant nibble */
#define BMP_MSN(value)  (unsigned char)(((value) & 0xf0) >> 4)  /* Most-significant nibble  */


/*  File Header Structure. */
/*  2   Type;       File Type Identifier             */
/*  4   FileSize;   Size of File                     */
/*  2   Reserved1;  Reserved (should be 0)           */
/*  2   Reserved2;  Reserved (should be 0)           */
/*  4   Offset;     Offset to bitmap data            */
/*  14  */

/*  Information Header Structure. */
/*  4  Size;            Size of Remaining Header         */
/*  4  Width;           Width of Bitmap in Pixels        */
/*  4  Height;          Height of Bitmap in Pixels       */
/*  2  Planes;          Number of Planes                 */
/*  2  BitCount;        Bits Per Pixel                   */
/*  4  Compression;     Compression Scheme (0=none)      */
/*  4  SizeImage;       Size of bitmap in 1s          */
/*  4  XPelsPerMeter;   Horz. Resolution in Pixels/Meter */
/*  4  YPelsPerMeter;   Vert. Resolution in Pixels/Meter */
/*  4  ClrUsed;         Number of Colors in Color Table  */
/*  4  ClrImportant;    Number of Important Colors       */
/*  40  */


/*  RGB Color Quadruple Structure. */
/*  1   rgbBlue;      Blue Intensity Value   */
/*  1   rgbGreen;     Green Intensity Value  */
/*  1   rgbRed;       Red Intensity Value    */
/*  1   rgbReserved;  Reserved (should be 0) */
/*  4  */


typedef struct _imFileBMP
{
  binFile* file_bin;          /* the binary file handle */
  unsigned short bpp;         /* number of bits per pixel */
  unsigned long offset,       /* image data offset, used only when reading */
                compress,     /* bmp compression information */
                line_size;    /* line buffer size */
  unsigned char* line_buffer; /* line buffer */
  int is_os2;                 /* indicates an os2 1.x BMP */
  unsigned long rmask, gmask, bmask, roff, goff, boff; /* pixel bit mask control when reading 16 and 32 bpp images */
  int top2bottom;
} imFileBMP;


static long imBMPScanBytes(int width, int bpp)
{
  return ((width * bpp + 31) / 32) * 4;
}


static int imBMPDecodeScanLine(binFile* file_bin, unsigned char* DecodedBuffer, int Width)
{
  unsigned char runCount;   /* Number of pixels in the run  */
  unsigned char runValue;   /* Value of pixels in the run   */
  int Index = 0, resto;     /* The index of DecodedBuffer   */
  int cont = 1;

  while (cont)
  {
    binFileRead1ByteBuf(file_bin, &runCount, 1);  /* Number of pixels in the run */
    binFileRead1ByteBuf(file_bin, &runValue, 1);  /* Value of pixels in the run  */

    if (runCount)
    {
      while (runCount-- && Index < Width)
        DecodedBuffer[Index++] = runValue;
    }
    else  /* Abssolute Mode or Escape Code */
    {
      switch(runValue)
      {
      case 0:             /* End of Scan Line Escape Code */
      case 1:             /* End of Bitmap Escape Code */
        cont = 0;
        break;
      case 2:             /* Delta Escape Code (ignored) */
        binFileRead1ByteBuf(file_bin, &runCount, 1);
        binFileRead1ByteBuf(file_bin, &runCount, 1);  
        break;
      default:            /* Abssolute Mode */
        resto = runValue % 2;
        runValue = (unsigned char)(Index + runValue < (Width + 1)? runValue: (Width - 1) - Index);
        binFileRead1ByteBuf(file_bin, DecodedBuffer + Index, runValue);
        if (resto) 
          binFileSeekOffset(file_bin, 1);
        Index += runValue;
      }
    }

    if (binFileError(file_bin) || Index > Width)
      return IM_ERR_READ;
  }

  return IM_ERR_NONE;
}


static int imBMPEncodeScanLine(unsigned char* EncodedBuffer, unsigned char* sl, int np)
{
  int slx = 0;           /* Scan line index */
  int state = BMP_READING;   /* State machine control variable */
  int count;             /* Used by various states */
  unsigned char pixel;            /* Holds single pixels from sl */
  int done = 0;          /* Ends while loop when true */
  int oldcount, oldslx;  /* Copies of count and slx */
  int BufSize = 0;

  while (!done) 
  {
    switch (state) 
    {
    case BMP_READING:
      /* Input: */
      /* np == number of pixels in scan line */
      /* sl == scan line */
      /* sl[slx] == next pixel to process */

      if (slx >= np)                      /* No pixels left */
        state = BMP_ENDOFLINE;
      else if (slx == np - 1)             /* One pixel left */
      {
        count = 1;
        state = BMP_SINGLE;
      } 
      else if (sl[slx] == sl[slx + 1])    /* Next 2 pixels equal */
        state = BMP_ENCODING;
      else                                /* Next 2 pixels differ */
        state = BMP_ABSMODE;

      break;
    case BMP_ENCODING:
      /* Input: */
      /* slx <= np - 2 (at least 2 pixels in run) */
      /* sl[slx] == first pixel of run */
      /* sl[slx] == sl[slx + 1] */

      count = 2;
      pixel = sl[slx];
      slx += 2;

      while ((slx < np) && (pixel == sl[slx]) && (count < 255)) 
      {
        count++;
        slx++;
      }

      *EncodedBuffer++ = (unsigned char)count; 
      BufSize++;
      *EncodedBuffer++ = pixel; 
      BufSize++;
      state = BMP_READING;
      
      break;
    case BMP_ABSMODE:
      /* Input: */
      /* slx <= np - 2 (at least 2 pixels in run) */
      /* sl[slx] == first pixel of run */
      /* sl[slx] != sl[slx + 1] */

      oldslx = slx;
      count = 2;
      slx += 2;

      /* Compute number of bytes in run */
      while ((slx < np) && (sl[slx] != sl[slx - 1]) && (count < 255)) 
      {
        count++;
        slx++;
      }

      /* If same-color run found, back up one byte */
      if ((slx < np) && (sl[slx] == sl[slx - 1]))
        if (count > 1)
          count--;

      slx = oldslx;  /* Restore scan-line index */

      /* Output short absolute runs of less than 3 pixels */
      if (count < 3 )
        state = BMP_SINGLE;
      else 
      {
        /* Output absolute-mode run */
        *EncodedBuffer++ = 0; 
        BufSize++;
        *EncodedBuffer++ = (unsigned char)count; 
        BufSize++;
        oldcount = count;

        while (count > 0) 
        {
          *EncodedBuffer++ = sl[slx]; 
          BufSize++;
          slx++;
          count--;
        }

        if (oldcount % 2) 
        {
          *EncodedBuffer++ = 0; 
          BufSize++;
        }

       state = BMP_READING;
      }
      break;

    case BMP_SINGLE:
      /* Input: */
      /* count == number of pixels to output */
      /* slx < np */
      /* sl[slx] == first pixel of run */
      /* sl[slx] != sl[slx + 1] */

      while (count > 0) 
      {
        *EncodedBuffer++ = (unsigned char)1; 
        BufSize++;
        *EncodedBuffer++ = sl[slx]; 
        BufSize++;
        slx++;
        count--;
      }

      state = BMP_READING;

      break;
    case BMP_ENDOFLINE:
      *EncodedBuffer++ = (unsigned char)0; 
      BufSize++;
      *EncodedBuffer++ = (unsigned char)0; 
      BufSize++;
      done = 1;

      break;
    default:
      break;
    }
  }

  return BufSize;
}


static imFile* imBMPReadOpen(char* filename, int *err)
{
  unsigned short id;
  unsigned long dword;
  imFile *ifile = (imFile*)malloc(sizeof(imFile));
  imFileBMP *file_bmp = (imFileBMP*)malloc(sizeof(imFileBMP));
  binFile* file_bin;

  ifile->file = file_bmp;

  /* opens the binary file for reading with intel byte order */
  file_bin = binFileOpen(filename, 0, 0);
  if (file_bin == NULL)
  {
    free(file_bmp);
    free(ifile);
    if (err) *err = IM_ERR_OPEN;
    return NULL;
  }

  file_bmp->file_bin = file_bin;

  /* reads the BMP format identifier */
  binFileRead2BytesBuf(file_bin, &id, 1);
  if (binFileError(file_bin))
  {
    binFileClose(file_bin);
    free(file_bmp);
    free(ifile);
    if (err) *err = IM_ERR_READ;
    return NULL;
  }

  if (id != BMP_ID)
  {
    binFileClose(file_bin);
    free(file_bmp);
    free(ifile);
    if (err) *err = IM_ERR_FORMAT;
    return NULL;
  }

  /* jump 8 bytes */
  binFileSeekOffset(file_bin, 8);

  /* reads the image offset */
  binFileRead4BytesBuf(file_bin, &file_bmp->offset, 1);

  /* reads the header dword */
  binFileRead4BytesBuf(file_bin, &dword, 1);

  if (dword == 40)
    file_bmp->is_os2 = 0;
  else if (dword == 12)
    file_bmp->is_os2 = 1;
  else
  {
    binFileClose(file_bin);
    free(file_bmp);
    free(ifile);
    if (err) *err = IM_ERR_FORMAT;
    return NULL;
  }

  /* reads the image width */
  binFileRead4BytesBuf(file_bin, &dword, 1);
  ifile->width = dword;

  /* reads the image height */
  binFileRead4BytesBuf(file_bin, &dword, 1);
  ifile->height = dword;

  file_bmp->top2bottom = 0;
  if (ifile->height < 0)
    file_bmp->top2bottom = 1;

  /* jump 2 bytes */
  binFileSeekOffset(file_bin, 2);

  /* reads the number of bits per pixel */
  binFileRead2BytesBuf(file_bin, &file_bmp->bpp, 1);

  if (binFileError(file_bin))
  {
    binFileClose(file_bin);
    free(file_bmp);
    free(ifile);
    if (err) *err = IM_ERR_READ;
    return NULL;
  }

  if (file_bmp->bpp > 8)
    ifile->type = IM_RGB;
  else
    ifile->type = IM_MAP;

  switch (file_bmp->bpp)
  {
  case 1:
    ifile->pal_size = 2;
    break;
  case 4:
    ifile->pal_size = 16;
    break;
  case 8:
    ifile->pal_size = 256;
    break;
  case 16:
  case 24:
  case 32:
    ifile->pal_size = 0;
    break;
  default:
    file_bmp->line_buffer = NULL;
    ifile->pal_size = 0;
    if (err) *err = IM_ERR_TYPE;
    return ifile;
  }

  if (file_bmp->is_os2 == 0)
  {
    /* reads the compression information */
    binFileRead4BytesBuf(file_bin, &file_bmp->compress, 1);

    switch (file_bmp->compress)
    {
    case BMP_BITFIELDS:
      if (file_bmp->bpp != 16 && file_bmp->bpp != 32)
      {
        file_bmp->line_buffer = NULL;
        ifile->compress = IM_NONE;
        if (err) *err = IM_ERR_TYPE;
        return ifile;
      }
    case BMP_COMPRESS_RGB:
      ifile->compress = IM_NONE;
      break;
    case BMP_COMPRESS_RLE8:
      ifile->compress = IM_DEFAULT;
      break;
    case BMP_COMPRESS_RLE4:
    default:
      file_bmp->line_buffer = NULL;
      ifile->compress = IM_COMPRESSED;
      if (err) *err = IM_ERR_COMP;
      return ifile;
    }

    if (imResolutionCB)
    {
      double xres = 0, yres = 0;
      int res_unit = IM_RES_DPC;

      /* jump 4 bytes */
      binFileSeekOffset(file_bin, 4);

      /* read the x resolution */
      binFileRead4BytesBuf(file_bin, &dword, 1);
      xres = (double)dword / 100.;

      /* read the y resolution */
      binFileRead4BytesBuf(file_bin, &dword, 1);
      yres = (double)dword / 100.;

      imResolutionCB(filename, &xres, &yres, &res_unit);
    }
    else
    {
      /* jump 12 bytes */
      binFileSeekOffset(file_bin, 12);
    }

    /* reads the number of colors used */
    binFileRead4BytesBuf(file_bin, &dword, 1);

    /* updates the pal_size based on the number of colors used */
    if (dword != 0 && (int)dword < ifile->pal_size)
      ifile->pal_size = dword;
  }
  else
  {
    file_bmp->compress = BMP_COMPRESS_RGB;
    ifile->compress = IM_NONE;
  }

  if (binFileError(file_bin))
  {
    binFileClose(file_bin);
    free(file_bmp);
    free(ifile);
    if (err) *err = IM_ERR_READ;
    return NULL;
  }

  /* now initialize the imFile control data */

  ifile->split = 0;     /* image is RGB packed */
  ifile->planes[0] = 0; /* sets the default plane order since split = 0 */
  ifile->planes[1] = 1;
  ifile->planes[2] = 2;
  ifile->pre_colors = 1;  /* color map is before the image */

  if (file_bmp->top2bottom)
    ifile->cur_line = ifile->height - 1;  /* first line */
  else
    ifile->cur_line = 0;  /* first line */

  /* Calculate the dword of the scan line buffer in bytes */
  file_bmp->line_size = imBMPScanBytes(ifile->width, file_bmp->bpp);
  file_bmp->line_buffer = (unsigned char*)malloc(file_bmp->line_size);

  if (err) *err = IM_ERR_NONE;

  return ifile;
}


static imFile* imBMPWriteOpen(char* filename, int width, int height, int type, int compress, int pal_size, int *err)
{
  unsigned short word_value;
  unsigned long dword_value;
  imFile *ifile = (imFile*)malloc(sizeof(imFile));
  imFileBMP *file_bmp = (imFileBMP*)malloc(sizeof(imFileBMP));
  binFile* file_bin;

  ifile->file = file_bmp;

  /* opens the binary file for writing with intel byte order */
  file_bin = binFileOpen(filename, 1, 0);
  if (file_bin == NULL)
  {
    free(file_bmp);
    free(ifile);
    if (err) *err = IM_ERR_OPEN;
    return NULL;
  }

  file_bmp->file_bin = file_bin;

  ifile->width = width;
  ifile->height = height;
  ifile->type = type;
  ifile->pal_size = (type == IM_RGB)? 0: pal_size;
  ifile->compress = compress;

  /* now initialize the imFile control data */

  ifile->split = 0;     /* image is RGB packed */
  ifile->planes[0] = 0; /* sets the default plane order since split = 0 */
  ifile->planes[1] = 1;
  ifile->planes[2] = 2;
  ifile->cur_line = 0;  /* first line */
  ifile->pre_colors = 1;  /* color map is before the image */

  switch (ifile->compress)
  {
  case IM_NONE:
    file_bmp->compress = BMP_COMPRESS_RGB;
    break;
  case IM_DEFAULT:
    if (ifile->type == IM_RGB)
      file_bmp->compress = BMP_COMPRESS_RGB;
    else
      file_bmp->compress = BMP_COMPRESS_RLE8;
    break;
  case IM_COMPRESSED:
    if (ifile->type == IM_RGB)
    {
      binFileClose(file_bin);
      free(file_bmp);
      free(ifile);
      if (err) *err = IM_ERR_COMP;
      return NULL;
    }
    else
      file_bmp->compress = BMP_COMPRESS_RLE8;
    break;
  }

  file_bmp->bpp = (unsigned short)((ifile->type == IM_RGB)? 24:8);

  /* writes the BMP file header */

  word_value = BMP_ID;
  binFileWrite2BytesBuf(file_bin, &word_value, 1); /* identifier */
  dword_value = 14 + 40 + ((type == IM_RGB)? 1: pal_size) * 4 + file_bmp->line_size * ifile->height;
  binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* file size for uncompressed images */
  word_value = 0;
  binFileWrite2BytesBuf(file_bin, &word_value, 1); /* reserved 1 */
  binFileWrite2BytesBuf(file_bin, &word_value, 1); /* reserved 2 */
  dword_value = 14 + 40 + ((type == IM_RGB)? 1: pal_size) * 4;
  binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* data offset */

  /* writes the BMP info header */

  dword_value = 40;
  binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* header size */
  dword_value = ifile->width;
  binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* width */
  dword_value = ifile->height;
  binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* height */
  word_value = 1;
  binFileWrite2BytesBuf(file_bin, &word_value, 1); /* planes */
  word_value = file_bmp->bpp;
  binFileWrite2BytesBuf(file_bin, &word_value, 1); /* bpp */
  dword_value = file_bmp->compress;
  binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* compression */
  dword_value = file_bmp->line_size * ifile->height;
  binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* image size */
  
  if (imResolutionCB)
  {
    double xres = 0, yres = 0;
    int res_unit = IM_RES_NONE;

    imResolutionCB(filename, &xres, &yres, &res_unit);

    if (res_unit != IM_RES_NONE)
    {
      if (res_unit == IM_RES_DPI)
      {
        xres /= 2.54;
        yres /= 2.54;
      }

      /* write the x resolution */
      dword_value = (unsigned long)(xres * 100);
      binFileWrite4BytesBuf(file_bin, &dword_value, 1);

      /* write the y resolution */
      dword_value = (unsigned long)(yres * 100);
      binFileWrite4BytesBuf(file_bin, &dword_value, 1);
    }
    else
    {
      dword_value = 0;
      binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* x dpm */
      binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* y dpm */
    }
  }
  else
  {
    dword_value = 0;
    binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* x dpm */
    binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* y dpm */
  }

  dword_value = ifile->pal_size;
  binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* colors in table */
  dword_value = ifile->pal_size;
  binFileWrite4BytesBuf(file_bin, &dword_value, 1); /* colors used */

  /* the RGB BMP has one dummy color in its palette data */
  if (ifile->type == IM_RGB)
    	binFileWrite1ByteBuf(file_bin, (unsigned char*)"\0\0\0\0", 4);

  /* tests if everything was ok */
  if (binFileError(file_bin))
  {
    binFileClose(file_bin);
    free(file_bmp);
    free(ifile);
    if (err) *err = IM_ERR_WRITE;
    return NULL;
  }

  /* Calculate the size of the scan line buffer in bytes */
  file_bmp->line_size = imBMPScanBytes(ifile->width, file_bmp->bpp);
  /* the comppression buffer needs to be bigger than the image line */
  file_bmp->line_buffer = (unsigned char*)malloc(file_bmp->line_size + 512);

  if (err) *err = IM_ERR_NONE;

  return ifile;
}


static void imBMPClose(imFile* ifile)
{
  imFileBMP *file_bmp = (imFileBMP*)ifile->file;
  binFile* file_bin = file_bmp->file_bin;

  binFileClose(file_bin);

  if (file_bmp->line_buffer) free(file_bmp->line_buffer);
  free(file_bmp);
  free(ifile);
}


static int imBMPReadColors(imFile* ifile, long *colors)
{
  int c, i, nc;
  unsigned char bmp_colors[256 * 4];
  imFileBMP *file_bmp = (imFileBMP*)ifile->file;
  binFile* file_bin = file_bmp->file_bin;

  /* jump the 2 headers */
  if (file_bmp->is_os2)
  {
    nc = 3;
    binFileSeekTo(file_bin, 14 + 12);
  }
  else
  {
    nc = 4;
    binFileSeekTo(file_bin, 14 + 40);
  }

  /* reads the color palette */
  binFileRead1ByteBuf(file_bin, bmp_colors, ifile->pal_size * nc);

  if (binFileError(file_bin))
    return IM_ERR_READ;

  /* convert the color map to the IM format */
  for (c = 0; c < ifile->pal_size; c++)
  {
    i = c * nc;
    colors[c] = imEncodeColor(bmp_colors[i + 2], bmp_colors[i + 1], bmp_colors[i]);
  }

  return IM_ERR_NONE;
}


static int imBMPWriteColors(imFile* ifile, long *colors)
{
  int c, i;
  unsigned char bmp_colors[256 * 4];
  imFileBMP *file_bmp = (imFileBMP*)ifile->file;
  binFile* file_bin = file_bmp->file_bin;

  /* convert the color map to the IM format */
  for (c = 0; c < ifile->pal_size; c++)
  {
    i = c * 4;                       
    imDecodeColor(&bmp_colors[i + 2], &bmp_colors[i + 1], &bmp_colors[i], colors[c]);
    bmp_colors[i + 3] = 0;
  }

  /* writes the color palette */
  binFileWrite1ByteBuf(file_bin, bmp_colors, ifile->pal_size * 4);

  if (binFileError(file_bin))
    return IM_ERR_WRITE;

  return IM_ERR_NONE;
}


static int imBMPReadLine(imFile* ifile, unsigned char *map)
{
  int x;
  imFileBMP *file_bmp = (imFileBMP*)ifile->file;
  binFile* file_bin = file_bmp->file_bin;

  /* if is the first, line jump to the begin of image data */
  if (ifile->cur_line == 0)
    binFileSeekTo(file_bin, file_bmp->offset);

  /* read and decompress the data */
  switch (file_bmp->compress)
  {
  case BMP_COMPRESS_RGB:
    binFileRead1ByteBuf(file_bin, file_bmp->line_buffer, file_bmp->line_size);
    if (binFileError(file_bin))
      return IM_ERR_READ;     
    break;
  case BMP_COMPRESS_RLE8:
    if (imBMPDecodeScanLine(file_bin, file_bmp->line_buffer, ifile->width) == IM_ERR_READ)
      return IM_ERR_READ;     
    break;
  }

  /* convert the buffer in the bmp bpp to im 8 bpp. */
  switch (file_bmp->bpp)
  {
  case 1:
    for (x = 0; x < ifile->width; x++)
      map[x] = (unsigned char)((file_bmp->line_buffer[x / 8] >> (7 - x % 8)) & 0x01);
    break;
  case 4:
    for (x = 0; x < ifile->width; x++)
      map[x] = (unsigned char)((file_bmp->line_buffer[x / 2] >> ((1 - x % 2) * 4)) & 0x0F);
    break;
  case 8:
    memcpy(map, file_bmp->line_buffer, ifile->width);
    break;
  }

  /* we have just read one more line */
  if (file_bmp->top2bottom)
    ifile->cur_line--;
  else
    ifile->cur_line++;

  return IM_ERR_NONE;
}


static int imBMPWriteLine(imFile* ifile, unsigned char *map)
{
  int line_size;
  imFileBMP *file_bmp = (imFileBMP*)ifile->file;
  binFile* file_bin = file_bmp->file_bin;

  /* compress and write the data */
  /* the compressed buffer size will probably be diferent from the uncompressed buffer size */
  switch (file_bmp->compress)
  {
  case BMP_COMPRESS_RGB:
    memcpy(file_bmp->line_buffer, map, ifile->width);
    line_size = file_bmp->line_size;
    break;
  case BMP_COMPRESS_RLE8:
    line_size = imBMPEncodeScanLine(file_bmp->line_buffer, map, ifile->width);
    break;
  }

  binFileWrite1ByteBuf(file_bin, file_bmp->line_buffer, line_size);
  if (binFileError(file_bin))
    return IM_ERR_WRITE;     

  /* we have just write one more line */
  ifile->cur_line++;

  return IM_ERR_NONE;
}


static int imBMPReadLineRGB(imFile* ifile, unsigned char *red, unsigned char *green, unsigned char *blue)
{
  int x, xpos;
  unsigned long dword_value;
  unsigned short word_value;
  imFileBMP *file_bmp = (imFileBMP*)ifile->file;
  binFile* file_bin = file_bmp->file_bin;

  /* if is the first, line jump to the begin of image data */
  if (ifile->cur_line == 0)
  {
    if (file_bmp->bpp == 16 || file_bmp->bpp == 32)
    {
      if (file_bmp->compress == BMP_BITFIELDS)
      {
        unsigned long Mask;
        unsigned long palette[3];

        binFileRead4BytesBuf(file_bin, palette, 3);
        if (binFileError(file_bin))
          return IM_ERR_READ;

        file_bmp->roff = 0;
        file_bmp->rmask = Mask = palette[0];
        while (!(Mask & 0x01) && (Mask != 0))
          {Mask >>= 1; file_bmp->roff++;}

        file_bmp->goff = 0;
        file_bmp->gmask = Mask = palette[1];
        while (!(Mask & 0x01) && (Mask != 0))
          {Mask >>= 1; file_bmp->goff++;}

        file_bmp->boff = 0;
        file_bmp->bmask = Mask = palette[2];
        while (!(Mask & 0x01) && (Mask != 0))
          {Mask >>= 1; file_bmp->boff++;}
      }
      else
      {
        if (file_bmp->bpp == 16)
        {                   
          file_bmp->rmask = 0x7C00;
          file_bmp->roff = 10;

          file_bmp->gmask = 0x03E0;
          file_bmp->goff = 5;

          file_bmp->bmask = 0x001F;
          file_bmp->boff = 0;
        }
        else
        {
          file_bmp->rmask = 0x00FF0000;
          file_bmp->roff = 16;

          file_bmp->gmask = 0x0000FF00;
          file_bmp->goff = 8;

          file_bmp->bmask = 0x000000FF;
          file_bmp->boff = 0;
        }
      }
    }

    binFileSeekTo(file_bin, file_bmp->offset);
  }

  /* reads the image line data */
  binFileRead1ByteBuf(file_bin, file_bmp->line_buffer, file_bmp->line_size);
  if (binFileError(file_bin))
    return IM_ERR_READ;

  /* convert the bmp line to the IM line */
  switch (file_bmp->bpp)
  {
  case 24:
    for (x = 0; x < ifile->width; x++)
    {
      xpos = x * 3;
      blue[x]  = file_bmp->line_buffer[xpos];
      green[x] = file_bmp->line_buffer[xpos + 1];
      red[x]   = file_bmp->line_buffer[xpos + 2];
    }
    break;
  case 16:
    /* inverts the WORD values if not intel */
	  if (binCPUByteOrder() == 1)
	     binInvertOrder2BytesBuf((unsigned short*)file_bmp->line_buffer, ifile->width);

    for (x = 0; x < ifile->width; x++)
    {
      word_value = ((unsigned short*)file_bmp->line_buffer)[x];
      red[x] = (unsigned char)((((file_bmp->rmask & word_value) >> file_bmp->roff) * 255) / (file_bmp->rmask >> file_bmp->roff));
      green[x] = (unsigned char)((((file_bmp->gmask & word_value) >> file_bmp->goff) * 255) / (file_bmp->gmask >> file_bmp->goff));
      blue[x] = (unsigned char)((((file_bmp->bmask & word_value) >> file_bmp->boff) * 255) / (file_bmp->bmask >> file_bmp->boff));
    }
    break;
  case 32:
    for (x = 0; x < ifile->width; x++)
    {
      dword_value = ((unsigned long*)file_bmp->line_buffer)[x];
      red[x] = (unsigned char)((file_bmp->rmask & dword_value) >> file_bmp->roff);
      green[x] = (unsigned char)((file_bmp->gmask & dword_value) >> file_bmp->goff);
      blue[x] = (unsigned char)((file_bmp->bmask & dword_value) >> file_bmp->boff);
    }
    break;
  }

  /* we have just read one more line */
  if (file_bmp->top2bottom)
    ifile->cur_line--;
  else
    ifile->cur_line++;

  return IM_ERR_NONE;
}


static int imBMPWriteLineRGB(imFile* ifile, unsigned char *red, unsigned char *green, unsigned char *blue)
{
  int x, xpos;
  imFileBMP *file_bmp = (imFileBMP*)ifile->file;
  binFile* file_bin = file_bmp->file_bin;

  for (x = 0; x < ifile->width; x++)
  {
    xpos = x * 3;
    file_bmp->line_buffer[xpos]     = blue[x];
    file_bmp->line_buffer[xpos + 1] = green[x];
    file_bmp->line_buffer[xpos + 2] = red[x];
  }

  /* writes the image line data */
  binFileWrite1ByteBuf(file_bin, file_bmp->line_buffer, file_bmp->line_size);
  if (binFileError(file_bin))
    return IM_ERR_WRITE;

  /* we have just write one more line */
  ifile->cur_line++;

  return IM_ERR_NONE;
}


static int imBMPRegisterCallback(imCallback cb, int id)
{
  (void)cb;
  (void)id;
  /* callback not found */
  return 0;
}


static imFormat _imFormatBMP =
{
  0,
  imBMPRegisterCallback,
  imBMPReadOpen,
  imBMPWriteOpen,
  imBMPClose,
  imBMPReadColors,
  imBMPWriteColors,
  imBMPReadLine,
  imBMPWriteLine,
  imBMPReadLineRGB,
  imBMPWriteLineRGB
};

imFormat* imFormatBMP(void)
{
  return &_imFormatBMP;
}
